/*
 * Copyright 2013 Glassy Cinema.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package hu.glassy.core.service.impl;

import hu.glassy.core.model.Account;
import hu.glassy.core.model.ActivationToken;
import hu.glassy.core.repositories.AccountRepository;
import hu.glassy.core.repositories.ActivationTokenRepository;
import hu.glassy.core.service.AccountService;
import hu.glassy.core.service.EmailService;
import hu.glassy.core.util.PageableHelper;
import hu.glassy.web.error.ActivationTokenException;
import hu.glassy.web.error.EmailSendingException;
import hu.glassy.web.util.PasswordHelper;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.apache.wicket.model.ResourceModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.mail.MailException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

/**
 * {@link AccountService} implementációja
 *
 * @author molnar.alexander@sonrisa.hu
 */
@Service
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService
{

  private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);

  @Autowired
  private AccountRepository accountRepository;

  @Autowired
  private ActivationTokenRepository aTokenRepository;

  @Autowired
  private EmailService emailService;

  @Value("${activation.resend.interval}")
  private int resendInterval;

  @Value("${activation.required}")
  private boolean activationRequired;

  @Value("${password.generated.length}")
  private int generatedPasswordLength;

  public Account findByEmailAndPassword(final String email, final String encryptedPassword)
  {
    return accountRepository.findByEmailAndPassword(email, encryptedPassword);
  }

  public Account findById(final Long id)
  {
    return accountRepository.findOne(id);
  }

  public boolean isEmailInUse(final String email)
  {
    return accountRepository.countByEmail(email) > 0;
  }

  public boolean isEmailAddressInUse(final String email)
  {
    return accountRepository.countByEmail(email) > 0;
  }

  @Transactional(readOnly = false)
  public void updateLastLoginDate(final Account account)
  {
    account.setLastLogin(new Date());
    accountRepository.save(account);
  }

  @Transactional(readOnly = false)
  public void registration(final Account account) throws ActivationTokenException
  {

    if (!activationRequired)
    {
      account.setActive(Boolean.TRUE);
    }

    accountRepository.save(account);

    if (activationRequired)
    {
      resendActivationKey(account);
    }
  }

  @Transactional(readOnly = false)
  public void resetPassword(final String email) throws EmailSendingException
  {
    final Account account = accountRepository.findByEmailIgnoreCase(email);
    Assert.notNull(account);

    final String password = PasswordHelper.generateRandomPassword(generatedPasswordLength);

    account.setPassword(PasswordHelper.encrypt(password));

    try
    {
      emailService.sendPasswordResetEmail(account, password);
      LOGGER.debug("Password reset e-mail was sent to address '{}'.", account.getEmail());

    }
    catch (final MailException e)
    {
      LOGGER.warn("Sending a password reset e-mail to address '{}' has failed.", account.getEmail());
      LOGGER.warn(e.getMessage());

      throw new EmailSendingException(new ResourceModel("passwordreset.email.fail").getObject(), e);
    }

    accountRepository.save(account);
  }

  @Transactional(readOnly = false)
  public void updateAccount(final Account account) throws ActivationTokenException
  {
    Assert.notNull(account);

    final Account old = accountRepository.findOne(account.getId());
    Assert.notNull(old, "The passed account was not found in the database!");

    // Ha valtozott az e-mail cim, akkor ujra kell aktivalni a fiokot
    if (activationRequired && !old.getEmail().equals(account.getEmail()))
    {
      account.setActive(Boolean.FALSE);
      resendActivationToken(account, false);
    }

    accountRepository.save(account);
  }

  @Transactional(readOnly = false)
  public void activate(final Account account, final String key) throws ActivationTokenException
  {
    final ActivationToken current = aTokenRepository.findActiveToken(account);

    if (current == null)
    {
      throw new ActivationTokenException(new ResourceModel("activation.error.token.wasnotfound").getObject());
    }

    if (!current.getToken().equals(key))
    {
      throw new ActivationTokenException(new ResourceModel("activation.error.token.notvalid").getObject());
    }

    current.useToken();
    aTokenRepository.save(current);

    account.setActive(Boolean.TRUE);
    accountRepository.save(account);
  }

  @Transactional(readOnly = false)
  public void inactivate(final Account account)
  {
    Assert.notNull(account);
    Assert.notNull(accountRepository.findOne(account.getId()), "The passed account was not found in the database!");

    account.setActive(Boolean.FALSE);
    accountRepository.save(account);
  }

  @Transactional(readOnly = false)
  public void resendActivationKey(final Account account) throws ActivationTokenException
  {
    resendActivationToken(account, true);
  }

  public boolean isEmailActivationRequired()
  {
    return activationRequired;
  }

  /**
   * @param account
   *                      Felhasználói fiók
   * @param checkInterval
   *                      Jelzi, hogy két újraküldés közti időtartamot figyelembe kell-e venni
   *
   * @throws ActivationTokenException
   */
  private void resendActivationToken(final Account account, final boolean checkInterval)
          throws ActivationTokenException
  {
    // Inactivate current token
    final ActivationToken current = aTokenRepository.findActiveToken(account);
    if (current != null)
    {
      // ha szükséges az utolsó kiküldés dátumának ellenőrzése
      if (checkInterval)
      {

        final Calendar cal = Calendar.getInstance();
        cal.setTime(current.getDateSent());
        cal.add(Calendar.MINUTE, resendInterval);

        // Megfelelo idonek el kell telni az aktivacios kulcs ujrakuldese elott
        if (new Date().before(cal.getTime()))
        {
          throw new ActivationTokenException(new ResourceModel("activation.resend.fail.date").getObject());
        }
      }

      current.setActive(Boolean.FALSE);
      aTokenRepository.save(current);
    }

    // Create new token
    final ActivationToken token = new ActivationToken(account);
    aTokenRepository.save(token);

    try
    {
      emailService.sendActivationEmail(account, token);
      LOGGER.debug("Activation e-mail was sent to address '{}'.", account.getEmail());

    }
    catch (final MailException e)
    {
      LOGGER.warn("Sending an activation e-mail to address '{}' has failed.", account.getEmail());
      LOGGER.warn(e.getMessage());

      throw new ActivationTokenException(new ResourceModel("activation.resend.fail.email").getObject());
    }

  }

  public Long countActiveAccounts()
  {
    return accountRepository.countByActive();
  }

  public List<Account> listActiveAccounts(final long start, final long size, final boolean asc, final String... sortProperties)
  {
    final Pageable pageable = PageableHelper.generatePageRequest(start, size, asc, sortProperties);

    return accountRepository.findByActive(true, pageable);
  }
}
